package guda.mrpc.core.server;

import guda.mrpc.core.RequestKey;
import guda.mrpc.core.config.ServerConfig;
import guda.mrpc.core.registry.ServiceMeta;
import guda.mrpc.core.registry.ServiceRegistry;
import guda.mrpc.core.transport.tcp.TcpServerChannelInitializer;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.WriteBufferWaterMark;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.util.concurrent.DefaultThreadFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.nio.channels.spi.SelectorProvider;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ThreadFactory;

/**
 * Created by well on 17/5/23.
 */
public class Server {

    private static final Logger log = LoggerFactory.getLogger(Server.class);

    protected ServiceProviderContainer serviceProviderContainer = new DefaultServiceProviderContainer();

    protected ServiceRegistry serviceRegistry;

    protected ServerConfig serverConfig;

    public void registerService(RequestKey requestKey,Object service){
        serviceProviderContainer.registerService(requestKey.toString(), service);
        ServiceMeta serviceMeta = new ServiceMeta();
        serviceMeta.setRequestKey(requestKey);
        serviceMeta.setPort(serverConfig.getPort());
        serviceMeta.setHost(serverConfig.getHost());
        serviceRegistry.register(serviceMeta);
    }

    public void registerService(Map<RequestKey,Object> serviceMap){
        if(serviceMap == null){
            return;
        }
        Iterator<Map.Entry<RequestKey, Object>> iterator = serviceMap.entrySet().iterator();
        while(iterator.hasNext()){
            Map.Entry<RequestKey, Object> next = iterator.next();
            registerService(next.getKey(),next.getValue());
        }
    }



    public void startServer() {

        ThreadFactory threadRpcFactory = new DefaultThreadFactory("mrpc-server");
        int parallel = Runtime.getRuntime().availableProcessors() * 2;

        EventLoopGroup boss = new NioEventLoopGroup();
        EventLoopGroup worker = new NioEventLoopGroup(parallel, threadRpcFactory, SelectorProvider.provider());

        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(boss, worker).channel(NioServerSocketChannel.class)
                    .childHandler(new TcpServerChannelInitializer(this))
                    .option(ChannelOption.SO_BACKLOG, serverConfig.getBacklog())
                    .childOption(ChannelOption.SO_KEEPALIVE, serverConfig.isKeepalive())
                    .childOption(ChannelOption.WRITE_BUFFER_WATER_MARK, new WriteBufferWaterMark(serverConfig.getLowWaterMark(),
                            serverConfig.getHighWaterMark()));


                ChannelFuture future = bootstrap.bind(serverConfig.getHost(), serverConfig.getPort()).sync();

//                for (String serviceName : serviceProviderContainer.getAllServices()) {
//                    serviceRegistry.register(serviceName);
//                    log.info("register service => [{}] - [{}]", serviceName, serverConfig.getServerAddr());
//                }
//                log.info("publish services finished!");
                log.info("mrpc server start with => {}", serverConfig.getPort());

                future.channel().closeFuture().sync();

        } catch (Exception e) {
            e.printStackTrace();
            log.error("start rpc server error", e);
        } finally {
            worker.shutdownGracefully();
            boss.shutdownGracefully();
        }
    }


    public ServiceProviderContainer getServiceProviderContainer() {
        return serviceProviderContainer;
    }

    protected void destroy() {
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            for (String serviceName : serviceProviderContainer.getAllServices()) {
                //serviceRegistry.unregister(serviceName);
                log.debug("unregister => [{}] - [{}]", serviceName, serverConfig.getServerAddr());
            }
        }));
    }

    public ServiceRegistry getServiceRegistry() {
        return serviceRegistry;
    }

    public void setServiceRegistry(ServiceRegistry serviceRegistry) {
        this.serviceRegistry = serviceRegistry;
    }

    public ServerConfig getServerConfig() {
        return serverConfig;
    }

    public void setServerConfig(ServerConfig serverConfig) {
        this.serverConfig = serverConfig;
    }
}
